package com.sdhs.file.utils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import com.google.common.collect.Multimap;
import com.sdhs.file.config.MinioConfig;

import io.minio.BucketExistsArgs;
import io.minio.CopyObjectArgs;
import io.minio.CopySource;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MakeBucketArgs;
import io.minio.ObjectStat;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.PutObjectOptions;
import io.minio.RemoveObjectArgs;
import io.minio.StatObjectArgs;
import io.minio.UploadObjectArgs;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidBucketNameException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.http.Method;
import io.minio.messages.ListPartsResult;
import io.minio.messages.Part;
import lombok.extern.slf4j.Slf4j;

/**
 * @author fangqisong
 * @describe MinIo服务器
 */
@Slf4j
@Component
@Configuration
@ConditionalOnMissingBean(MinioConfig.class)
@EnableConfigurationProperties({MinioConfig.class})
public class MinioTemplate {

    private MinioConfig minioProperties;

    private MzMinioClient minioClient;

    public MinioTemplate(MinioConfig minIo) {
        this.minioProperties = minIo;
    }

//    @PostConstruct
    public void init() throws Exception {
        minioClient = new MzMinioClient(minioProperties.getUrl(), minioProperties.getAccessKey(), minioProperties.getSecretKey());
        String defaultBucketName = minioProperties.getBucketName();
        makeBucket(defaultBucketName);
    }

    public MinioConfig getConfig() {
        log.info("minio-url===={}", this.minioProperties.getUrl());
        return this.minioProperties;
    }

    /**
     * 创建文件夹
     *
     * @param bucketName 文件夹名
     * @return
     */
    public void makeBucket(String bucketName) throws Exception {
        boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder()
                .bucket(bucketName)
                .build());
        if (!isExist) {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        }
    }

    /**
     * 功能描述: 文件上传
     *
     * @param bucketName    文件夹名
     * @param fileName      上传后的文件名
     * @param multipartFile multipartFile
     * @return
     */
    public String putObjectUrl(String bucketName, String fileName, MultipartFile multipartFile) throws Exception {
        bucketName = com.sdhs.common.utils.StringUtils.defaultString(bucketName, minioProperties.getBucketName());
        putObject(bucketName, fileName, multipartFile);
        return getObjectUrl(bucketName, fileName);
    }

    /**
     * 文件上传
     *
     * @param bucketName    文件夹名
     * @param fileName      上传后的文件名
     * @param multipartFile multipartFile
     * @return
     */
    public void putObject(String bucketName, String fileName, MultipartFile multipartFile) throws Exception {
        // 先判断文件夹是否存在
        makeBucket(bucketName);
        // PutObjectOptions，上传配置(文件大小，内存中文件分片大小)
        PutObjectOptions putObjectOptions = new PutObjectOptions(multipartFile.getSize(), PutObjectOptions.MIN_MULTIPART_SIZE);
        // 文件的ContentType
        putObjectOptions.setContentType(multipartFile.getContentType());
        InputStream inputStream = multipartFile.getInputStream();
        minioClient.putObject(PutObjectArgs.builder()
                .contentType(multipartFile.getContentType())
                .stream(inputStream, multipartFile.getSize(), -1)
                .bucket(bucketName)
                .object(fileName)
                .build());
        IOUtils.closeQuietly(inputStream);
    }

    /**
     * 获取文件的Url地址
     *
     * @param bucketName 文件夹名
     * @param fileName   上传后的文件名
     * @return
     */
    public String getObjectUrl(String bucketName, String fileName) throws Exception {
        String url = minioClient.getObjectUrl(bucketName, fileName);
        return url;
    }

    /**
     * 获取文件的Url地址
     *
     * @param bucketName 文件夹名
     * @param fileName   上传后的文件名
     * @return
     */
    public String getPresignedObjectUrl(String bucketName, String fileName, int expiry) throws Exception {
        String url = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(fileName)
                        .expiry(expiry)
                        .build());
        return url;
    }


    /**
     * 本地文件上传
     *
     * @param bucketName 文件夹名称
     * @param fileName   上传后的文件名
     * @param filePath   本地文件路径
     * @return
     */
    public void putObject(String bucketName, String fileName, String filePath) throws Exception {
        // 先判断文件夹是否存在
        makeBucket(bucketName);
        minioClient.uploadObject(UploadObjectArgs.builder()
                .bucket(bucketName)
                .filename(filePath)
                .object(fileName)
                .build());
    }

    /**
     * 本地文件上传并返回URL地址
     *
     * @param bucketName 文件夹名称
     * @param fileName   上传后的文件名
     * @param filePath   本地文件路径
     * @return
     */
    public String putObjectUrl(String bucketName, String fileName, String filePath) throws Exception {
        putObject(bucketName, fileName, filePath);
        return getObjectUrl(bucketName, fileName);
    }


    /**
     * 文件流上传文件
     *
     * @param bucketName 文件夹的名称
     * @param fileName   上传后的文件名
     * @param stream     文件流
     * @return
     */
    public void putObject(String bucketName, String fileName, InputStream stream) throws Exception {
        // 先判断文件夹是否存在
        makeBucket(bucketName);
        minioClient.putObject(PutObjectArgs.builder()
                .stream(stream, stream.available(), -1)
                .bucket(bucketName)
                .object(fileName)
                .build());
    }

    /**
     * 文件流上传文件并返回Url
     *
     * @param bucketName 文件夹名
     * @param fileName   上传后的文件名称
     * @param stream     文件流
     * @return
     */
    public String putObjectUrl(String bucketName, String fileName, InputStream stream) throws Exception {
        putObject(bucketName, fileName, stream);
        return getObjectUrl(bucketName, fileName);
    }

    /**
     * 判断文件是否存在
     *
     * @param bucketName 文件夹
     * @param fileName   文件名
     * @return
     */
    public boolean fileExists(String bucketName, String fileName) throws Exception {
        ObjectStat file = minioClient.statObject(StatObjectArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .build());
        if (file != null) {
            return file.length() > 0 ? true : false;
        } else {
            return false;
        }
    }


    /**
     * 文件下载
     *
     * @param bucketName   文件夹
     * @param fileName     文件名称
     * @param originalName 下载的文件名
     * @param response     response
     * @return
     */
    public void downloadFile(String bucketName, String fileName, String originalName, String suffix, HttpServletResponse response) {
        InputStream inputStream;
        try {
            inputStream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .build());
            if (StringUtils.isNotEmpty(originalName)) {
                fileName = originalName;
            }
            response.reset();
            response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(originalName, "UTF-8"));
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            String fileContent = FileContentType.contentType(suffix);
            response.setContentType(fileContent);
            FileUtil.writeFile(inputStream, response.getOutputStream());
        } catch (Exception e) {
            log.error("下载 异常>>{}", e);
        }
    }


    /**
     * 文件下载
     *
     * @param bucketName   文件夹
     * @param fileName     文件名称
     * @param originalName 下载的文件名
     * @param response     response
     * @return
     */
    public InputStream downloadFileInputStream(String bucketName, String fileName, String originalName, String suffix, HttpServletResponse response) {
        InputStream inputStream = null;
        try {
            inputStream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .build());

        } catch (Exception e) {
            log.error("下载 异常>>{}", e);
        }
        return inputStream;
    }



    /**
     * 删除指定的文件
     *
     * @param bucketName 文件夹
     * @param fileName   上传后的文件名
     * @return
     */
    public void removeObject(String bucketName, String fileName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .build());
    }

    /**
     * 拷贝指定文件到指定地方
     *
     * @param bucketName     源文件夹
     * @param objectName     源文件
     * @param destBucketName 目标文件夹
     * @param destObjectName 目标文件
     * @throws Exception
     */
    public void copyObject(String bucketName, String objectName, String destBucketName, String destObjectName) throws Exception {
        minioClient.copyObject(CopyObjectArgs.builder()
                .bucket(destBucketName)
                .object(destObjectName)
                .source(
                        CopySource.builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .build())
                .build());
    }

    /**
     * 分片上传,申请上传标识，返回uploadId,每次分片上传内唯一,同一个uploadID的分片文件,认为是一个同一个大文件的
     */
    public String uploadId(String bucketName, String region, String objectName, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws Exception {
        makeBucket(bucketName);
        return minioClient.createMultipartUpload(bucketName, region, objectName, headers, extraQueryParams);
    }

    /**
     * 获取预签名的文件上传链接
     *
     * @param bucketName  文件夹名
     * @param fileName    上传后的文件名
     * @param queryParams 查询参数
     * @return
     **/

    public String getPresignedObjectUrl(String bucketName, String fileName, Map<String, String> queryParams) throws Exception {
        makeBucket(bucketName);
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(fileName)
                        .expiry(60 * 60 * 24)// 24小时
                        .extraQueryParams(queryParams)
                        .build());
    }

    /**
     * 查询分片数据
     *
     * @param bucketName 存储桶
     * @param objectName 对象名
     * @param uploadId   上传ID
     */
    public ListPartsResult listParts(String bucketName, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException, InvalidBucketNameException {
        return minioClient.listParts(bucketName, objectName, maxParts, partNumberMarker, uploadId);
    }

    /**
     * 完成分片上传，执行合并文件
     *
     * @param bucketName 存储桶
     * @param objectName 对象名
     * @param uploadId   上传ID
     * @param parts      分片
     */
    public ObjectWriteResponse completeMultipartUpload(String bucketName, String objectName, String uploadId, Part[] parts) throws Exception {
        return minioClient.completeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);
    }


    /**
     * 获取文件的流
     *
     * @param args
     * @return
     * @throws Exception
     */
    public InputStream getObject(GetObjectArgs args) throws Exception {
        InputStream inputStream = minioClient.getObject(args);
        return inputStream;
    }

}
